package rouletteSelection

import (
	"bytes"
	"fmt"
	"gitee.com/kingzyt/common/log"
	"gitee.com/kingzyt/common/objPool"
	"gitee.com/kingzyt/common/util"
	"math"
	"math/rand"
)

type SelectElem interface {
	GetRate() float32
	GetTick() float32
	SetTick(tick float32)
	String() string
}

type RouletteSelection struct {
	name        string
	elems       []SelectElem
	totalTick   float32
	maxRateElem SelectElem
}

func NewRouletteSelection(name string, elems *[]SelectElem) (*RouletteSelection, error) {
	rlt := &RouletteSelection{name: name}
	err := rlt.Reset(elems)
	if err != nil {
		return nil, err
	}

	return rlt, nil
}
func NewRouletteSelectionUnsetElems(name string) *RouletteSelection {
	return &RouletteSelection{name: name}
}

func (self *RouletteSelection) Clean() {
	self.elems = nil
	self.totalTick = 0
	self.maxRateElem = nil
}

func (self *RouletteSelection) Reset(elems_ *[]SelectElem) (err error) {
	elems := *elems_
	defer func() {
		*elems_ = elems
	}()
	if len(elems) == 0 {
		return fmt.Errorf("%s, no elem", self.name)
	}

	// remove 0 rate elems
	cursor := 0
	for i := 0; i < len(elems); i++ {
		if elems[i].GetRate() <= 0 {
			continue
		} else {
			if i > cursor {
				elems[cursor] = elems[i]
			}
			cursor++
		}
	}
	if cursor == 0 {
		return fmt.Errorf("%s, all elem rates are 0", self.name)
	}
	if cursor != len(elems) {
		elems = elems[:cursor]
	}

	var maxRateElem SelectElem
	maxRate := float32(-math.MaxFloat32)

	totalTick := float32(0)
	for i := 0; i < len(elems); i++ {
		e := elems[i]
		rate := e.GetRate()
		if rate > maxRate {
			maxRateElem = e
			maxRate = rate
		}
		totalTick += rate
		e.SetTick(totalTick)
	}

	if totalTick == 0 {
		return fmt.Errorf("%s, totalTick is 0", self.name)
	}

	if maxRateElem == nil {
		return fmt.Errorf("%s, max rate elem is nil", self.name)
	}

	self.elems = elems
	self.totalTick = totalTick
	self.maxRateElem = maxRateElem

	return nil
}

func (self *RouletteSelection) Rand() SelectElem {
	f := rand.Float32()
	tick := f * self.totalTick
	for i := 0; i < len(self.elems); i++ {
		e := self.elems[i]
		if tick < e.GetTick() {
			return e
		}
	}

	log.Error(self.name, "rand no result, bug? return maxRateElem, cursor tick:%f, randF:%f, totalTick:%f", tick, f, self.totalTick)
	return self.maxRateElem
}

func (self *RouletteSelection) IteratorElems(proc func(elem SelectElem) bool) {
	for i := 0; i < len(self.elems); i++ {
		if !proc(self.elems[i]) {
			return
		}
	}
}

func (self *RouletteSelection) String() string {
	info := &bytes.Buffer{}
	info.WriteString("[")
	for i := 0; i < len(self.elems); i++ {
		info.WriteString(self.elems[i].String())
		info.WriteString(";")
	}
	info.WriteString("]")
	return fmt.Sprintf("name:%s, totalTick:%.0f, %s", self.name, self.totalTick, info.String())
}

////////////////////////////////////////////////////////////////////////
type TempSelectElem struct {
	elem SelectElem
	tick float32
}

func (self *TempSelectElem) GetRate() float32 {
	return self.elem.GetRate()
}
func (self *TempSelectElem) GetTick() float32 {
	return self.tick
}
func (self *TempSelectElem) SetTick(tick float32) {
	self.tick = tick
}
func (self *TempSelectElem) String() string {
	return fmt.Sprintf("r:%.0f, t:%.0f", self.elem.GetRate(), self.tick)
}

var g_selectElemsPool = objPool.NewObjPool("g_selectElemsPool", 0, 0, func() interface{} {
	return []SelectElem{}
})
var g_tempSelectElemPool = objPool.NewObjPool("g_tempSelectElemPool", 0, 0, func() interface{} {
	return &TempSelectElem{}
})

// includeJudge return false, means the elem is excluded
func (self *RouletteSelection) RandsNoDup(count int32, includeJudge func(rlt SelectElem) bool, procOneRandRlt func(rlt SelectElem)) (err error) {
	if procOneRandRlt == nil {
		return
	}
	if count <= 0 {
		return fmt.Errorf("rand rlt count(%d) <= 0", count)
	}
	if count >= int32(len(self.elems)) {
		log.Warn(self.name, "rand no dup(%d) count is over elems len(%d), just return all elems", count, len(self.elems))
		for i := 0; i < len(self.elems); i++ {
			if includeJudge != nil && !includeJudge(self.elems[i]) {
				continue
			}
			procOneRandRlt(self.elems[i])
		}
		return
	}

	//log.Info(self.name, log.Trace, "rand no-dup, count:%d", count)

	list := g_selectElemsPool.Pull().([]SelectElem)
	defer func() {
		for i := 0; i < len(list); i++ {
			tempElem := list[i]
			if tempElem != nil {
				tempElem.(*TempSelectElem).elem = nil
				g_tempSelectElemPool.Push(tempElem)
				list[i] = nil
			}
		}
		g_selectElemsPool.Push(list)
	}()
	list = list[:0]
	for i := 0; i < len(self.elems); i++ {
		if includeJudge != nil && !includeJudge(self.elems[i]) {
			continue
		}
		tempElem := g_tempSelectElemPool.Pull().(*TempSelectElem)
		tempElem.elem = self.elems[i]
		list = append(list, tempElem)
	}

	count = util.Int32Min(count, int32(len(list)))

	rltCnt := 0
	for i := int32(0); i < count; i++ {
		curListLen := len(list) - rltCnt
		rltIdx, err := QuickRouletteRand(list[:curListLen])
		if err != nil {
			log.Error(self.name, "RandsNoDup err:%s", err)
			continue
		}

		procOneRandRlt(list[rltIdx].(*TempSelectElem).elem)
		tailIdx := curListLen - 1
		if rltIdx != tailIdx {
			temp := list[tailIdx]
			list[tailIdx] = list[rltIdx]
			list[rltIdx] = temp
		}
		rltCnt++
	}
	return
}

func AvgRandInt32NoDupWithModifyInputList(count int, list []int32, procOneRandRlt func(val int32)) {
	if count <= 0 || procOneRandRlt == nil {
		return
	}
	if count > len(list) {
		count = len(list)
	}

	rltCnt := 0
	for i := 0; i < count; i++ {
		curListLen := len(list) - rltCnt
		rltIdx := int(rand.Int31n(int32(curListLen)))
		procOneRandRlt(list[rltIdx])
		tailIdx := curListLen - 1
		if rltIdx != tailIdx {
			temp := list[tailIdx]
			list[tailIdx] = list[rltIdx]
			list[rltIdx] = temp
		}
		rltCnt++
	}
}
func AvgRandUint16NoDupWithModifyInputList(count int, list []uint16, procOneRandRlt func(val uint16)) {
	if count <= 0 || procOneRandRlt == nil {
		return
	}
	if count > len(list) {
		count = len(list)
	}

	rltCnt := 0
	for i := 0; i < count; i++ {
		curListLen := len(list) - rltCnt
		rltIdx := int(rand.Int31n(int32(curListLen)))
		procOneRandRlt(list[rltIdx])
		tailIdx := curListLen - 1
		if rltIdx != tailIdx {
			temp := list[tailIdx]
			list[tailIdx] = list[rltIdx]
			list[rltIdx] = temp
		}
		rltCnt++
	}
}

func QuickRouletteRand(elems []SelectElem) (rltIdx int, err error) {
	totalTick := float32(0)
	for i := 0; i < len(elems); i++ {
		e := elems[i]
		rate := e.GetRate()
		if rate <= 0 {
			return -1, fmt.Errorf("elem[%d] cannot be 0 rate", i)
		}
		totalTick += rate
		e.SetTick(totalTick)
	}

	tick := rand.Float32() * totalTick
	for i := 0; i < len(elems); i++ {
		if tick < elems[i].GetTick() {
			return i, nil
		}
	}

	return -1, fmt.Errorf("rand no result")
}

var g_weightsPool = objPool.NewObjPool("g_weightsPool", 0, 0, func() interface{} {
	return make([]float32, 0, 5)
})

func SimpleRouletteRand(weights []int32) (rltIdx int, err error) {
	ok := false
	for i := 0; i < len(weights); i++ {
		if weights[i] > 0 {
			ok = true
			break
		}
	}
	if !ok {
		return -1, fmt.Errorf("all weights are 0")
	}

	ticks := g_weightsPool.Pull().([]float32)
	defer func() {
		g_weightsPool.Push(ticks)
	}()
	ticks = ticks[:0]

	totalTick := float32(0)
	for i := 0; i < len(weights); i++ {
		totalTick += float32(weights[i])
		ticks = append(ticks, totalTick)
	}

	tick := rand.Float32() * totalTick
	for i := 0; i < len(ticks); i++ {
		if tick < ticks[i] {
			return i, nil
		}
	}

	return -1, fmt.Errorf("rand no result")
}

type ItemTypeSelectElem struct {
	ItemType int32
	Rate     float32
	tick     float32
}

func (self *ItemTypeSelectElem) GetRate() float32 {
	return self.Rate
}
func (self *ItemTypeSelectElem) GetTick() float32 {
	return self.tick
}
func (self *ItemTypeSelectElem) SetTick(tick float32) {
	self.tick = tick
}
func (self *ItemTypeSelectElem) String() string {
	return fmt.Sprintf("%d, r:%.0f, t:%.0f", self.ItemType, self.Rate, self.tick)
}

type CountSelectElem struct {
	Count int32
	Rate  float32
	tick  float32
}

func (self *CountSelectElem) GetRate() float32 {
	return self.Rate
}
func (self *CountSelectElem) GetTick() float32 {
	return self.tick
}
func (self *CountSelectElem) SetTick(tick float32) {
	self.tick = tick
}
func (self *CountSelectElem) String() string {
	return fmt.Sprintf("cnt:%d, r:%.0f, t:%.0f", self.Count, self.Rate, self.tick)
}

type IdCountSelectElem struct {
	Id    int32
	Count int32
	Rate  float32
	tick  float32
}

func (self *IdCountSelectElem) GetRate() float32 {
	return self.Rate
}
func (self *IdCountSelectElem) GetTick() float32 {
	return self.tick
}
func (self *IdCountSelectElem) SetTick(tick float32) {
	self.tick = tick
}
func (self *IdCountSelectElem) String() string {
	return fmt.Sprintf("id:%d, cnt:%d, r:%.0f, t:%.0f", self.Id, self.Count, self.Rate, self.tick)
}

type IdSelectElem struct {
	Id   int32
	Rate float32
	tick float32
}

func (self *IdSelectElem) GetRate() float32 {
	return self.Rate
}
func (self *IdSelectElem) GetTick() float32 {
	return self.tick
}
func (self *IdSelectElem) SetTick(tick float32) {
	self.tick = tick
}
func (self *IdSelectElem) String() string {
	return fmt.Sprintf("%d[%.0f]", self.Id, self.Rate)
	//return fmt.Sprintf("id:%d, r:%.0f, t:%.0f", self.Id, self.Rate, self.tick)
}
